// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/client/impl/JmHtmlClient.java
package dev.jukomu.jmcomic.client.impl;

import dev.jukomu.common.util.net.CommonResponse;
import dev.jukomu.common.util.net.IResponse;
import dev.jukomu.jmcomic.client.JmAlbumCommentResp;
import dev.jukomu.jmcomic.client.JmImageResp;
import dev.jukomu.jmcomic.config.JmMagicConstants;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.entity.JmAlbumDetail;
import dev.jukomu.jmcomic.entity.JmFavoritePage;
import dev.jukomu.jmcomic.entity.JmPhotoDetail;
import dev.jukomu.jmcomic.entity.JmSearchPage;
import dev.jukomu.jmcomic.exception.ExceptionUtils;
import dev.jukomu.jmcomic.util.JmPageUtils;
import dev.jukomu.jmcomic.util.JmcomicText;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 对应Python的JmHtmlClient，基于网页实现的JmcomicClient。
 */
public class JmHtmlClient extends AbstractJmClient {

    public static final String CLIENT_KEY = "html";
    private static final String API_SEARCH = "/search/photos";
    private static final String API_CATEGORY = "/albums";

    /**
     * 构造函数。
     *
     * @param postman    Postman实例。
     * @param domainList 域名列表。
     * @param retryTimes 重试次数。
     */
    public JmHtmlClient(dev.jukomu.common.postman.Postman postman, List<String> domainList, int retryTimes) {
        super(postman, domainList, retryTimes);
        afterInit();
    }

    @Override
    public String getClientKey() {
        return CLIENT_KEY;
    }

    @Override
    public IResponse addFavoriteAlbum(String albumId, String folderId) {
        Map<String, Object> data = new HashMap<>();
        data.put("album_id", albumId);
        data.put("fid", folderId);

        try {
            return getJmHtml("/ajax/favorite_album", false, Collections.singletonMap("data", data));
        } catch (Exception e) {
            throw new RuntimeException("Failed to add album to favorites", e);
        }
    }

    @Override
    public JmAlbumDetail getAlbumDetail(String albumId) {
        String validatedId = JmcomicText.parseToJmId(albumId);
        String cacheKey = "html_album_" + validatedId;

        return fetchFromCacheOrNetwork(cacheKey, () -> {
            IResponse resp = getJmHtml("/album/" + validatedId);
            return JmcomicText.analyseJmAlbumHtml(resp.getText());
        });
    }

    @Override
    public JmPhotoDetail getPhotoDetail(String photoId, boolean fetchAlbum, boolean fetchScrambleId) {
        String validatedId = JmcomicText.parseToJmId(photoId);
        String cacheKey = "html_photo_" + validatedId;

        JmPhotoDetail photo = fetchFromCacheOrNetwork(cacheKey, () -> {
            IResponse resp = getJmHtml("/photo/" + validatedId);
            return JmcomicText.analyseJmPhotoHtml(resp.getText());
        });
        if (fetchAlbum) {
            photo.setFromAlbum(getAlbumDetail(photo.getAlbumId()));
        }

        return photo;
    }

    @Override
    public void checkPhoto(JmPhotoDetail photo) {
        if (photo.getFromAlbum() == null) {
            photo.setFromAlbum(getAlbumDetail(photo.getAlbumId()));
        }
        if (photo.getPageArr() == null || photo.getDataOriginalDomain() == null) {
            JmPhotoDetail newPhoto = getPhotoDetail(photo.getPhotoId(), false, true);
            newPhoto.setFromAlbum(photo.getFromAlbum()); // Preserve the original album reference
            // Update the original photo object with fetched details
            photo.setPageArr(newPhoto.getPageArr());
            photo.setDataOriginalDomain(newPhoto.getDataOriginalDomain());
            photo.setDataOriginal0(newPhoto.getDataOriginal0());
        }
    }

    @Override
    public JmSearchPage search(String searchQuery, int page, int mainTag, String orderBy, String time, String category, String subCategory) {
        String cacheKey = String.format("html_search_%s_%d_%d_%s_%s_%s_%s",
                searchQuery, page, mainTag, orderBy, time, category, subCategory);

        return fetchFromCacheOrNetwork(cacheKey, () -> {
            Map<String, Object> params = new HashMap<>();
            params.put("main_tag", mainTag);
            params.put("search_query", searchQuery);
            params.put("page", page);
            params.put("o", orderBy);
            params.put("t", time);

            String url = buildSearchUrl(API_SEARCH, category, subCategory);

            // Use AbstractJmClient's get method which includes retry logic
            IResponse resp = null;
            try {
                resp = get(url, Collections.singletonMap("params", params));
            } catch (Exception e) {
                throw new RuntimeException("Search request failed", e);
            }

            // 检查重定向，如果搜索的是ID
            String respUrl = resp.getUrl();
            if (resp instanceof CommonResponse && ((CommonResponse) resp).getRedirectCount() > 0 && respUrl.contains("/album/")) {
                JmAlbumDetail album = JmcomicText.analyseJmAlbumHtml(resp.getText());
                return JmSearchPage.wrapSingleAlbum(album);
            } else {
                return JmPageUtils.parseHtmlToSearchPage(resp.getText());
            }
        });
    }

    /**
     * 构建网页搜索/分类的URL。
     *
     * @param base        "/search/photos" 或 "/albums"
     * @param category    类别
     * @param subCategory 副类别
     * @return 完整的URL路径
     */
    private String buildSearchUrl(String base, String category, String subCategory) {
        if (category == null || category.equals(JmMagicConstants.CATEGORY_ALL)) {
            return base;
        }
        if (subCategory == null || subCategory.isEmpty()) {
            return base + "/" + category;
        } else {
            return base + "/" + category + "/sub/" + subCategory;
        }
    }

    @Override
    public JmSearchPage categoriesFilter(int page, String time, String category, String orderBy, String subCategory) {
        String cacheKey = String.format("html_categories_%d_%s_%s_%s_%s",
                page, time, category, orderBy,subCategory);
        return fetchFromCacheOrNetwork(cacheKey, () -> {
            Map<String, Object> params = new HashMap<>();
            params.put("page", page);
            params.put("o", orderBy);
            params.put("t", time);

            String url = buildSearchUrl(API_CATEGORY, category, subCategory);

            try {
                IResponse resp = get(url, Collections.singletonMap("params", params));
                return JmPageUtils.parseHtmlToCategoryPage(resp.getText());
            } catch (Exception e) {
                throw new RuntimeException("Categories filter request failed", e);
            }
        });
    }

    @Override
    public IResponse login(String username, String password) {
        this.username = username; // 缓存用户名
        Map<String, Object> data = new HashMap<>();
        data.put("username", username);
        data.put("password", password);
        data.put("id_remember", "on");
        data.put("login_remember", "on");
        data.put("submit_login", "");

        try {
            IResponse resp = post("/login", new HashMap<String, Object>() {{
                put("data", data);
                put("allow_redirects", false);
            }});
            if (resp.getHttpCode() != 200) {
                ExceptionUtils.raisesResp("登录失败，状态码为" + resp.getHttpCode(), resp);
            }

            // 更新Postman的cookies
            // OkHttp的CookieJar会自动处理，但如果没用CookieJar，需要手动更新metaData
            // 假设使用了OkHttpSessionPostman，CookieJar会自动处理
            // 如果是OkHttpPostman，则需要手动将resp.headers().get("Set-Cookie")解析并存入metaData
            // 这里我们假设底层的Postman实现（特别是SessionPostman）会处理Cookie
            return resp;
        } catch (Exception e) {
            throw new RuntimeException("Login request failed", e);
        }
    }

    @Override
    public JmFavoritePage getFavoriteFolder(int page, String orderBy, String folderId, String username) {
        String user = (username != null && !username.isEmpty()) ? username : this.username;
        dev.jukomu.common.util.Asserts.requireTrue(user != null && !user.isEmpty(), "favoriteFolder方法需要传username参数或先登录");

        Map<String, Object> params = new HashMap<>();
        params.put("page", page);
        params.put("o", orderBy);
        params.put("folder", folderId);

        IResponse resp = getJmHtml("/user/" + user + "/favorite/albums", true, Collections.singletonMap("params", params));
        return JmPageUtils.parseHtmlToFavoritePage(resp.getText());
    }

    @Override
    public JmAlbumCommentResp albumComment(String videoId, String comment, String originator, String status, String commentId) {
        Map<String, Object> data = new HashMap<>();
        data.put("video_id", videoId);
        data.put("comment", comment);
        data.put("originator", originator);
        data.put("status", status);

        if (commentId != null) {
            data.remove("status");
            data.put("comment_id", commentId);
            data.put("is_reply", 1);
            data.put("forum_subject", 1);
        }

        JmModuleConfig.jmLog("album.comment", String.format(
                "%s: [%s]%s", videoId, comment, (commentId != null ? " to (" + commentId + ")" : "")
        ));

        try {
            IResponse resp = post("/ajax/album_comment", Collections.singletonMap("data", data));
            JmAlbumCommentResp commentResp = new JmAlbumCommentResp(resp.getRawResponse());
            JmModuleConfig.jmLog("album.comment", String.format(
                    "%s: [%s] ← (%s)", videoId, comment, commentResp.getModel().get("cid")
            ));
            return commentResp;
        } catch (Exception e) {
            throw new RuntimeException("Failed to post album comment", e);
        }
    }

    @Override
    public JmImageResp downloadImage(String imgUrl, String imgSavePath, String scrambleId, boolean decodeImage) throws IOException {
        dev.jukomu.jmcomic.client.JmImageResp resp = getJmImage(imgUrl);
        resp.requireSuccess();
        resp.transferTo(imgSavePath, scrambleId, decodeImage, imgUrl);
        return resp;
    }

    @Override
    public dev.jukomu.jmcomic.client.JmImageResp downloadByImageDetail(dev.jukomu.jmcomic.entity.JmImageDetail image, String imgSavePath, boolean decodeImage) throws IOException {
        return downloadImage(image.getDownloadUrl(), imgSavePath, image.getScrambleId(), decodeImage);
    }

    @Override
    public dev.jukomu.jmcomic.client.JmImageResp getJmImage(String imgUrl) {
        try {
            IResponse resp = get(imgUrl, new HashMap<String, Object>() {{
                put("headers", JmModuleConfig.newHtmlHeaders()); // 使用HTML请求头
            }});
            return new dev.jukomu.jmcomic.client.JmImageResp(resp.getRawResponse());
        } catch (Exception e) {
            throw new RuntimeException("Failed to get JM image", e);
        }
    }

    /**
     * 获取禁漫网页的通用方法。
     *
     * @param url        路径
     * @param require200 是否要求HTTP状态码为200
     * @param kwargs     请求参数
     * @return 响应对象IResponse
     */
    protected IResponse getJmHtml(String url, boolean require200, Map<String, Object> kwargs) {
        try {
            IResponse resp = get(url, kwargs);
            if (require200 && resp.getHttpCode() != 200) {
                checkSpecialHttpCode(resp);
                ExceptionUtils.raisesResp("Request failed with status code: " + resp.getHttpCode(), resp);
            }
            checkSpecialText(resp);
            checkRedirectError(resp, url);
            return resp;
        } catch (Exception e) {
            throw new RuntimeException("Failed to get JM HTML from " + url, e);
        }
    }

    protected IResponse getJmHtml(String url) {
        return getJmHtml(url, true, null);
    }

    @Override
    protected void updateRequestWithSpecifyDomain(Map<String, Object> kwargs, String domain, boolean isImage) {
        if (isImage) {
            return; // 图片请求不修改域名相关的header
        }
        // 对于网页请求，确保headers中包含正确的authority, origin, referer
        Map<String, String> headers = (Map<String, String>) kwargs.computeIfAbsent("headers", k -> new HashMap<>());
        headers.put("authority", domain);
        headers.put("origin", JmModuleConfig.PROT + domain);
        headers.put("referer", JmModuleConfig.PROT + domain + "/");
    }

    /**
     * 检查响应是否包含特殊的错误文本。
     */
    private void checkSpecialText(IResponse resp) {
        String html = resp.getText();
        if (html.length() > 500) {
            return;
        }
        for (Map.Entry<String, String> entry : JmModuleConfig.JM_ERROR_RESPONSE_TEXT.entrySet()) {
            if (html.contains(entry.getKey())) {
                ExceptionUtils.raisesResp(String.format("%s (%s): %s", entry.getValue(), entry.getKey(), resp.getUrl()), resp);
            }
        }
    }

    /**
     * 检查是否是特殊的HTTP错误状态码。
     */
    private void checkSpecialHttpCode(IResponse resp) {
        String errorMsg = JmModuleConfig.JM_ERROR_STATUS_CODE.get(resp.getHttpCode());
        if (errorMsg != null) {
            ExceptionUtils.raisesResp(String.format("请求失败，原因为: [%s], URL=[%s]", errorMsg, resp.getUrl()), resp);
        }
    }

    /**
     * 检查重定向URL是否表示错误页面。
     */
    private void checkRedirectError(IResponse resp, String originalPath) {
        if (!(resp instanceof dev.jukomu.common.util.net.CommonResponse) || ((dev.jukomu.common.util.net.CommonResponse) resp).getRedirectCount() == 0) {
            return;
        }
        String respUrl = resp.getUrl();
        if (!respUrl.contains("/error/")) {
            return;
        }

        if (respUrl.endsWith("/error/album_missing") && !originalPath.endsWith("/error/album_missing")) {
            ExceptionUtils.raiseMissing(resp, JmcomicText.parseToJmId(originalPath));
        } else if (respUrl.endsWith("/error/user_missing") && !originalPath.endsWith("/error/user_missing")) {
            ExceptionUtils.raisesResp("此用戶名稱不存在，或者你没有登录，請再次確認使用名稱", resp);
        } else if (respUrl.endsWith("/error/invalid_module") && !originalPath.endsWith("/error/invalid_module")) {
            ExceptionUtils.raisesResp("發生了無法預期的錯誤。若問題持續發生，請聯繫客服支援", resp);
        }
    }
}